<?php
namespace Liaiyong\Light\Core;

use Liaiyong\Light\Base\Singleton;
use Liaiyong\Light\Core\Action;
use Liaiyong\Light\Core\Config;
use Liaiyong\Light\Core\EventEmitter;
use Liaiyong\Light\Core\Error;
use Liaiyong\Light\Core\Request;
use Liaiyong\Light\Data\DataError;
use Liaiyong\Light\Util\Logger;
use Liaiyong\Light\Util\Utility;

use Exception;
use ErrorException;

abstract class App extends Singleton {
    const E_BEFORE = 'app:event:before';
    const E_RUN = 'app:event:run';
    const E_AFTER = 'app:event:after';
    const E_FINISH = 'app:event:finish';
    const E_ERROR = 'app:event:error';
    public static $_rootpath;
    public static $_apppath;
    public static $_trustee;
    public static $_action;
    public static $_config;
    public static $_logger;
    public static $_event;
    public static $_app;
    //
    private static $_finished = false;
    /**
     * 运行，创建App并启动App的生命同期
     * 
     * @return void
     */
    public static function start($rootpath, $apppath, $action_prefix = '') {
        // ob start
        ob_start();
        ini_set('output_buffering', 'on');
        ini_set('implicit_flush', 'off');
        // app instance
        self::$_app = self::getInstance();
        // set error handler
        /*if(!Utility::isCli()) {
            set_error_handler(array(self::$_app, 'error_handler'));
        }*/
        // initialize root path
        self::$_rootpath = $rootpath;
        // initialize app document path, depend on root path
        self::$_apppath = $apppath;
        // initialize Logger, depend on document path
        self::$_logger = Logger::getInstance();
        self::$_logger->initialize();
        // initialize EventEmitter, depend on Logger
        self::$_event = EventEmitter::getInstance();
        self::$_event->initialize();
        // initialize Configuration, depend on Logger, EventEmitter
        self::$_config = Config::getInstance();
        self::$_config->initialize();
        // initialize App
        self::$_app->initialize();
        self::$_app->detect(self::$_apppath, $action_prefix);
        // lanuch lifecycle
        self::$_app->lifecycle();
    }
    /**
     * 调用完成事件
     * @param $callback
     * @param $params
     */
    public static function callbackFinished($callback, $params = array()) {
        if(self::$_finished) {
            // 直接调用
            call_user_func_array($callback, array_merge($params, array(self::$_app)));
        } else {
            // 添加监听
            self::$_event->listen(get_class(self::$_app), self::E_FINISH, $callback, $params);
        }
    }

    protected $prefix;
    protected $apiname = '';
    protected $extension = '';
    protected $classname = '';
    protected $start_time = 0;
    protected $finish_time = 0;
    protected $routers = array ();
    public function getStarttime() {
        return $this->start_time;
    }
    public function getApiname() {
        return $this->apiname;
    }
    public function getExtension() {
        return $this->extension;
    }
    public function getClassname() {
        return $this->classname;
    }
    public function getRouters() {
        return $this->routers;
    }
    protected final function __construct() {
        // 设置开始时间
        $this->start_time = microtime(true);
    }
    /**
     * App初始化,根据实际需求将成员变量classname赋值
     * 
     * @return void
     */
    public function initialize() {
    }
    /**
     * App生命同期
     * 
     * @return void
     */
    protected function lifecycle($fire = true) {
        $class = get_class($this);
        try {
            // before
            $this->before();
            self::$_event->fire($class, self::E_BEFORE, array($this));
            // run
            $this->run();
            self::$_event->fire($class, self::E_RUN, array($this));
            // after
            $this->after();
            self::$_event->fire($class, self::E_AFTER, array($this));
            // finish
            $this->finish();
            self::$_event->fire($class, self::E_FINISH, array($this));
        } catch (Exception $err) {
            // error
            self::$_event->fire($class, self::E_ERROR, array($this));
            if(App::get('display_errors', false)) {
                $this->error($err);
                throw $err;
            } else {
                $this->logAccess($err);
                $this->logError($err);
                $this->display($err);
            }
        }
    }
    protected function before() {
        try {
            if(empty($this->classname) || !class_exists($this->classname)) {
                throw Error::class_not_found($this->classname);
            }
        } catch(\Exception $err) {
            throw Error::api_not_exists($this->apiname);
        }
    }
    // override detect classname
    protected function detect($webpath, $prefix = '') {
        $prefix = empty($prefix)? '' : rtrim($prefix, '\\') . '\\';// 右侧补充“\”
        $webpath = realpath($webpath) . DIRECTORY_SEPARATOR;
        $script = realpath($_SERVER['SCRIPT_FILENAME']);
        if(!isset($_SERVER['REQUEST_URI']) || Utility::isCli()) {
            $name = basename(str_replace(DIRECTORY_SEPARATOR, '.', substr($script, strlen($webpath))), '.php');
            $this->apiname = $apiname = "/".trim(str_replace('\\', '/', $name), '/');
            $this->extension = 'php';
        } else {
            $uri = Utility::intersectClip(str_replace('\\', '/', $webpath), $_SERVER['REQUEST_URI']);
            $uri = '/' . ltrim($uri, '/');// 左侧补充“/”
            $pos = strpos($uri, '?');
            if($pos > 0) {
                $uri = substr($uri, 0, $pos);
            }
            $pathinfo = pathinfo($uri);
            extract($pathinfo);
            if(empty($extension)) {
                $this->apiname = $apiname = $uri;
            } else {
                $this->apiname = $apiname = rtrim($uri, '.' . $extension);
                $this->extension = $extension;
            }
        }
        // 
        $subject = preg_replace(array('/\//', '/\./', '/\-/'), array('\\', '\\', '_'), $apiname);
        $all = preg_match_all('/([\\\\|\/|\-|_])(\S)/', $subject, $matches, PREG_SET_ORDER);
        foreach ($matches as $match) {
            $str = $match[0];
            $subject = str_replace($str, strtoupper($str), $subject);// 首字母大写
        }
        if(empty($apiname) || $apiname == '/') {
            $this->classname = $classname = $prefix . "Index";
        } else {
            $this->classname = $classname = $prefix . trim($subject, '\\ ');
        }
        return $classname;
    }
    // main run
    protected function run() {
        $classname = $this->classname;
        if(!is_subclass_of($classname, 'Liaiyong\Light\Core\Action')) {
            throw Error::invalid_action_class($classname);
        } else {
            self::$_action = $classname::getInstance();
            self::$_action->initialize();
            self::$_action->lifecycle();
        }
        /*$routers = $this->routers;// 
        if (empty($routers)) {
        } else {
            throw Error::router_unsupported();
        }*/
    }
    protected function after() {
    }
    protected function finish() {
        // 结束时间, 不包含异步运行时间
        $start_time = $this->start_time;
        $finish_time = $this->finish_time = microtime(true);
        $apiname = $this->apiname;
        Logger::debug("{$apiname} {$start_time} {$finish_time} " . ($finish_time - $start_time), 'time');
        // fastcgi finish request
        if (function_exists('fastcgi_finish_request')) {
            // ngnix fastcgi
            fastcgi_finish_request();
        }
        self::$_finished = true;
    }
    /**
     * 输出运行异常信息
     */
    public function error($err) {
        print_r('<pre>');
        print_r($err->getMessage() . '(' . $err->getCode() . ")\n");
        print_r($err->getFile() . '(' . $err->getLine() . ")\n");
        print_r($err->getTraceAsString());
        $pre = $err->getPrevious();
        if(!empty($pre)) {
            $this->error($pre);
        }
    }
    public function logAccess($err) {
        $uri = $_SERVER['REQUEST_URI'];
        $pos = strpos($uri, '?');
        if($pos !== false) {
            $uri = substr($uri, 0, $pos);
        }
        $method = $_SERVER['REQUEST_METHOD'];
        $refer = empty($_SERVER['HTTP_REFERER']) ? '-' : $_SERVER['HTTP_REFERER'];
        $ua = $_SERVER['HTTP_USER_AGENT'];
        $logs = array(
            Utility::ip(), //
            '-', //
            '-', //
            Utility::accesstime(), //
            "\"$method $uri HTTP/1.1\"",//
            "\"$refer\"",//
            "\"$ua\""
        );
        self::$_logger->error(implode(' ', $logs));
    }
    /**
     * 输出运行异常JSON结果
     */
    public function logError($err) {
        $log = '';
        $log .= $err->getMessage() . '(' . $err->getCode() . ")\n";
        $log .= $err->getFile() . '(' . $err->getLine() . ")\n";
        $log .= $err->getTraceAsString() . "\n";
        self::$_logger->error($log);
        $pre = $err->getPrevious();
        if(!empty($pre)) {
            self::$_logger->error("Previous: ");
            $this->logError($pre);
        }
    }
    /**
     * 输出运行异常JSON结果
     */
    public function display($err) {
        $de = new DataError();//var_dump($err);
        $de->api = $this->apiname;//var_dump($de);
        $template = Template::getInstance();
        $template->push($de);//var_dump(Template::getInstance());exit;
        $template->display();
    }
    /**
     * PHP捕获异常句柄
     * 注：无效的
     */
    public function error_handler($errno, $errstr, $errfile, $errline) {
        // handle error
        //$err = 
        $this->error(new \Exception($errstr, $errno));
    }
    /**
     * 注：无效的
     */
    protected function errorIterator($err) {
        $log = '';
        if($err instanceof Exception) {
            $pre = $err->getPrevious();
            $log .= $err->getMessage() . '(' . $err->getCode() . ")\n";
            $log .= $err->getFile() . '(' . $err->getLine() . ")\n";
            $log .= $err->getTraceAsString() . "\n";
            if(!empty($pre)) {
                $log .= "Previous: " . $this->errorIterator($pre);
            }
        }
        return $log;
    }

    /**
     * 设置某个配置项
     *
     * @param string|array $keystr
     *            键名
     * @param string|boolean|int|array $value
     *            键值
     * @return void
     */
    public static function set($keystr, $value) {
        Config::set($keystr, $value);
    }
    /**
     * 获取某个配置项
     *
     * @param string $keystr
     *            键名，子键名配置项使用.号分割
     * @param mixed $default
     *            不存在时的默认值，默认null
     * @return mixed
     */
    public static function get($keystr = '', $default = null) {
        return Config::get($keystr, $default);
    }
}

// PHP END
